说明
通过文件系统 API,网页可以实现对磁盘内容的读写,进而可以实现类似与本地编辑器的效果。当然,作为网页,文件系统的访问也是会受到严格的权限控制的,只能访问用户选择的文件或目录,才能够进行访问。
文件系统访问API包括三个 API:
showOpenFilePicker()
显示一个文件选取器,用于让用户选择电脑中的一个或多个文件,并返回文件的句柄。showSaveFilePicker()
显示一个文件选取器,用于让用户保存文件,用户可以选择现有文件或输入新文件的名称。showDirectoryPicker()
显示一个目录选取器,用于让用户选择电脑中的一个目录,并返回目录的句柄。
三个 API 只能在安全的上下文中才能使用,并且必须由用户手动触发。
兼容性
截止目前(2022年2月25日),只有基于 Chrome 86+ 的浏览器(如 Chrome 86、Edge 86)支持文件系统访问API 。兼容性具体见 showOpenFilePicker | Can I use…
使用文件系统访问 API
从本地文件系统读取文件
调用 window.showOpenFilePicker()
,会显示一个文件选取器对话框,并提示用户选择文件。选择文件后,API 将返回一个文件句柄数组。可选参数会影响文件选取器的行为,例如允许用户选择多个文件或限制文件类型。默认情况下,文件选取器只允许用户选择单个文件。
用户选择文件后,showOpenFilePicker()
返回一个句柄数组,其中包含与该文件交互所需的属性和方法。
拥有文件句柄之后,便可以获取文件属性及读取文件内容。通过调用fileHandle.getFile()
,可以得到一个 File
文件对象。 File
继承自 Blob
,之后便可以通过.arrayBuffer()
、.text()
、.stream()
等方法获取数据,或者通过 .slice()
方法分割 Blob
对象。
openFileBtn.addEventListener('click', async () => {
// 让用户选择一个文件
const [fileHandle] = await window.showOpenFilePicker();
// 从文件句柄获得文件
const file = await fileHandle.getFile();
// 以文本格式读取文件读取文件内容
const contents = await file.text();
// 将内容放到 <textarea> 中
textArea.value = contents;
});
指定建议的启动目录
在许多情况下,你可能希望应用建议默认的位置。例如要构建文本编辑器,则可能需要在默认文档文件夹中启动文件保存或文件打开对话框,而对于图像编辑器,则可能需要在默认图片文件夹中启动。您可以通过 startIn
建议默认的启动目录。
const fileHandle = await self.showOpenFilePicker({
startIn: 'pictures'
});
已知系统目录的列表是:
desktop
:用户的桌面目录(如果存在此类内容)。documents
:用户创建的文档通常存储在其中的目录。downloads
:通常存储下载文件的目录。music
:通常存储音频文件的目录。pictures
:通常存储照片和其他静止图像的目录。videos
:通常存储视频/电影的目录。
除了已知的系统目录之外,还可以将现有文件或目录句柄作为 startIn
的值。然后,该对话框将在同一目录中打开。
将文件写入本地文件系统
要保存文件,需要调用showSaveFilePicker()
,它将在“保存”模式下显示文件选取器,允许用户选择要用于保存的新文件。
得到要保存的文件句柄后,便可以将文件保存到磁盘,不过,在此之前,我们需要先通过fileHandle.createWritable()
获得一个 FileSystemWritableFileStream
文件系统可写文件流对象,之后通过 Writable
文件系统可写文件流对象进行文件的保存。
在许多情况下,你可能希望应用建议默认文件名。则可以通过 suggestedName
选项的一部分传递来实现此目的。
async function save(contents) {
const options = {
// 建议的文件名
suggestedName: '新文件.txt',
// 例如文本编辑器,希望自动添加 .txt 扩展名
types: [
{
description: '文本文件',
accept: { 'text/plain': ['.txt'] },
},
],
};
const fileHandle = await window.showSaveFilePicker(options);
// 创建一个 FileSystemWritableFileStream 文件系统可写文件流对象
const writable = await fileHandle.createWritable();
// 将数据写入流
await writable.write(contents);
// 关闭流并将文件保存到磁盘
await writable.close();
}
注意 在流关闭之前,不会将更改写入磁盘。
分用途记录选择器打开的位置
有时,应用具有用于不同目的不同选取器。例如,富文本编辑器可能允许用户打开文本文件,但也允许用户导入图像。默认情况下,每个文件选取器都将在最后记住的位置打开。但可以通过存储每种类型的选取器的值来规避这种情况。如果指定了id
选项,相同 id
的文件选取器用单独一组最后记住的位置。
const textFileHandle = await self.showSaveFilePicker({
id: 'text',
});
const imageFileHandle = await self.showSaveFilePicker({
id: 'image',
});
打开目录并枚举其内容
要枚举目录中的所有文件,需要调用showDirectoryPicker()
为用户提供一个目录选取器来选取目录,再进行枚举。
const butDir = document.getElementById('butDirectory');
butDir.addEventListener('click', async () => {
const dirHandle = await window.showDirectoryPicker();
for await (const entry of dirHandle.values()) {
console.log(entry.kind, entry.name);
}
});
创建、访问、删除目录中的文件和目录及解析
通过目录的getFileHandle(path, options)
getDirectoryHandle(path, options)
方法分别用来获取文件及子目录,如果第二个参数中的 create
选项为 true
,则可以创建文件或目录。
通过目录的removeEntry(path, options)
方法来删除文件及子目录,如果第二个参数中的 recursive
选项为 true
,则可以进行递归删除。
解析目录中项目的路径
通过目录的resolve(handle)
方法,可以得到解析相关项目的路径。
存储的文件或目录句柄和权限
由于权限不在会话之间保留,因此应通过fileHandle.queryPermission()
验证用户是否已授予对文件或目录的权限。如果没有,则需要通过 fileHandle.requestPermission()
授权。如下面的verifyPermission
,就实现了此功能。
async function verifyPermission(fileHandle, readWrite) {
const options = {};
if (readWrite) {
options.mode = 'readwrite';
}
// 检查是否拥有权限,如果拥有,则返回 true
if ((await fileHandle.queryPermission(options)) === 'granted') {
return true;
}
// 请求授权,若授权,则返回 true
if ((await fileHandle.requestPermission(options)) === 'granted') {
return true;
}
// 用户没有授权,返回 false
return false;
}
拖放集成
HTML 拖放界面使 Web 应用程序能够接受在网页上拖放的文件。在拖放操作中,拖动的文件项和目录可以通过DataTransferItem.getAsFileSystemHandle()
获得。下面的清单显示了这一点的实际应用。请注意,拖放界面的 DataTransferItem.kind
,文件和目录的均为'file'
,而文件系统访问 API 的 FileSystemHandle.kind
,文件和目录则分别为'file'
和 'directory'
。
elem.addEventListener('dragover', (e) => {
// 禁用默认操作
e.preventDefault();
});
elem.addEventListener('drop', async (e) => {
e.preventDefault();
const fileHandlesPromises = [...e.dataTransfer.items]
// 过滤只保留文件/目录对应的拖拽项
.filter((item) => item.kind === 'file')
// 获得对应的文件句柄和目录句柄
.map((item) => item.getAsFileSystemHandle());
for await (const handle of fileHandlesPromises) {
if (handle.kind === 'directory') {
console.log(`Directory: ${handle.name}`);
} else {
console.log(`File: ${handle.name}`);
}
}
});
在 IndexedDB 中存储文件句柄或目录句柄
文件句柄和目录句柄是可序列化的,这意味着您可以将文件句柄或目录句柄保存到 IndexedDB,也可以调用 postMessage
以在同一顶级源之间发送它们。具体操作这里将不再赘述,可以查阅其他相关文档或教程。
API 介绍
async function showOpenFilePicker()
参数
options
:object
- 可选的选项对象:multiple
:boolean
- 是否允许用户选择多个文件,默认为false
excludeAcceptAllOption
:boolean
- 是否禁用文件类型过滤器,默认为false
types
:Array
- 文件过滤器列表,每一项都为包含下列属性的对象:description
:string
-描述文本
accept
:object
- 键为 MIME 类型,值为文件后缀的对象,例如{ 'image/*': ['.png', '.gif', '.jpeg', '.jpg'] }
startIn
:string
FileSystemFileHandle
FileSystemDirectoryHandle
- 启动目录,用于指定对话框打开后的默认目录,已知系统目录的列表是:desktop
:用户的桌面目录(如果存在此类内容)。documents
:用户创建的文档通常存储在其中的目录。downloads
:通常存储下载文件的目录。music
:通常存储音频文件的目录。pictures
:通常存储照片和其他静止图像的目录。videos
:通常存储视频/电影的目录。
也可以是
FileSystemFileHandle
文件句柄对象 或FileSystemDirectoryHandle
目录句柄对象id
:string
- 记录并保存对话框最后的位置,下次打开对话框,会读取上次相同id
的最后位置。
返回值
由 FileSystemFileHandle
文件句柄对象构成的数组
异常
AbortError
- 如果用户没有进行选择而是取消提示,或者选择的文件被认为太敏感或太危险而不能暴露给网站,则会引发此错误。
async function showSaveFilePicker()
参数
options
:object
- 可选的选项对象:suggestedName
:string
- 建议的文件名excludeAcceptAllOption
:boolean
- 是否禁用文件类型过滤器,默认为false
types
:Array
- 文件过滤器列表,详见showOpenFilePicker()
的types
选项startIn
:string
- 启动目录,详见showOpenFilePicker()
的startIn
选项id
:string
- 记录并保存对话框最后的位置,详见showOpenFilePicker()
的id
选项
返回值
一个 FileSystemFileHandle
文件句柄对象
异常
AbortError
- 如果用户在没有选择或输入文件的情况下关闭文件选择器,或者选择的文件被认为太敏感或太危险而不能暴露给网站,则会引发此错误。
async function showDirectoryPicker()
参数
options
:object
- 可选的选项对象:startIn
:string
- 启动目录,详见showOpenFilePicker()
的startIn
选项id
:string
- 记录并保存对话框最后的位置,详见showOpenFilePicker()
的id
选项
返回值
一个 FileSystemDirectoryHandle
目录句柄对象
异常
AbortError
- 如果用户没有进行选择而是取消提示,或者选择的文件被认为太敏感或太危险而不能暴露给网站,则会引发此错误。
FileSystemFileHandle
文件句柄对象
属性
继承了 FileSystemHandle
文件系统句柄对象的属性
方法
继承了 FileSystemHandle
文件系统句柄对象的方法
getFile()
会异步返回一个包含File
文件对象的Promise
createWritable()
会异步返回一个包含FileSystemWritableFileStream
对象的Promise
,该对象可用于写入文件。
FileSystemDirectoryHandle
目录句柄对象
属性
继承了 FileSystemHandle
文件系统句柄对象的属性
方法
继承了 FileSystemHandle
文件系统句柄对象的方法
entries()
返回给定对象自己的可枚举属性对[key, value]
的数组getFileHandle(path, options)
异步返回具有指定名称的文件的FileSystemFileHandle
文件句柄对象getDirectoryHandle(path, options)
异步返回在调用该方法的目录句柄中具有指定名称的子目录的FileSystemDirectoryHandle
目录句柄对象keys()
返回一个新的数组迭代器,其中包含FileSystemDirectoryHandle
中每个项的键。removeEntry(path, options)
尝试异步删除对应文件/目录。resolve(handle)
异步获取句柄相对于当前目录的路径数组,如['a','b.txt']
相当于a/b.txt
FileSystemHandle
文件系统句柄对象
属性
kind
如果关联的是文件,则为'file'
,若是目录,则为'directory'
name
所关联文件或目录的名称
方法
- `isSameEntry() 比较两个句柄所关联的文件/目录是否匹配
queryPermission()
查询当前句柄的当前权限状态。requestPermission()
请求读取或读取文件句柄的权限。remove()
删除关联的文件/目录